package db.swing2;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import de.dnb.basics.applicationComponents.DbProperties;
import de.dnb.basics.applicationComponents.DbUtils;
import de.dnb.basics.applicationComponents.Pair;
import de.dnb.basics.applicationComponents.RangeCheckUtils;

/**
 * 
 * Verbindung zu der Datenbank, die in ./dbconfig/db.properties eingetragen ist.
 * Die Verbindung* sollte über {@link #close()} geschlossen werden (obwohl
 * {@link #finalize()} auch die Verbindung schließt).
 * 
 * @author Christian_2
 * 
 */
public final class DBConnection {

	private final Connection connection;
	private final PreparedStatement statementSearchAllTypes;
	/**
	 * Suche nach Name und Typ, wg. trunkierung mit * abgeschlossen.
	 */
	private final PreparedStatement statementSearchNameTypeTrunk;
	private final PreparedStatement statementSearchID;
	private final PreparedStatement statementSearchUBB;
	private final PreparedStatement statementSearchOBB;

	/**
	 * Stellt eine Verbindung zu der Datenbank her, die in
	 * ./dbconfig/db.properties eingetragen ist. Bereitet die PreparedStatements
	 * vor. Die Verbindung sollte über {@link #close()} geschlossen werden
	 * (obwohl {@link #finalize()} auch die Verbindung schließt).
	 * 
	 * @throws SQLException
	 *             wenn sql-Fehler
	 */
	public DBConnection(String path) throws SQLException {
		DbProperties properties = new DbProperties(path);
		String url =
				properties.getUrl()
						+ ";ifexists=false;create=false;readonly=true";
		connection =
				DriverManager.getConnection(url, properties.getUserName(),
						properties.getPassword());
		statementSearchAllTypes =
				connection.prepareStatement(
						"SELECT type, level, name, id FROM sw "
								+ "WHERE name LIKE ?",
						ResultSet.TYPE_SCROLL_INSENSITIVE,
						ResultSet.CONCUR_READ_ONLY);
		statementSearchNameTypeTrunk =
				connection.prepareStatement("SELECT type, level, name, id "
						+ "FROM sw WHERE type = ? AND name LIKE ? ",
						ResultSet.TYPE_SCROLL_INSENSITIVE,
						ResultSet.CONCUR_READ_ONLY);
		statementSearchID =
				connection.prepareStatement(
						"SELECT type, level, name, id FROM sw WHERE id = ?",
						ResultSet.TYPE_SCROLL_INSENSITIVE,
						ResultSet.CONCUR_READ_ONLY);

		statementSearchUBB =
				connection
						.prepareStatement("SELECT id, name "
								+ "FROM sw JOIN rel ON rel.ub = sw.id "
								+ "WHERE rel.ob = ? " + "ORDER BY name",
								ResultSet.TYPE_FORWARD_ONLY,
								ResultSet.CONCUR_READ_ONLY);
		statementSearchOBB =
				connection
						.prepareStatement("SELECT id, name "
								+ "FROM sw JOIN rel ON rel.ob = sw.id "
								+ "WHERE rel.ub = ? " + "ORDER BY name",
								ResultSet.TYPE_FORWARD_ONLY,
								ResultSet.CONCUR_READ_ONLY);
	}

	/**
	 * Erstellt das ResultSet für eine Tabelle, die die Treffer der trunkierten
	 * Suche nach einem Schlagwort enthält.
	 * 
	 * @param name
	 *            nicht null, kann aber auch leer sein, dann wird die gesamte
	 *            Datenbank angezeigt.
	 * 
	 * @return Modell oder null (bei sql-Fehler)
	 */
	public synchronized ResultSet getResultSetForName(String name) {
		RangeCheckUtils.assertReferenceParamNotNull("name", name);
		// verschiedene utf-8-Formen von Umlauten etc. vereinheitlichen:
		name = Normalizer.normalize(name, Form.NFD);
		ResultSet resultSetSearchName = null;
		try {
			if (name.endsWith("*")) {
				name = name.substring(0, name.length() - 1) + '%';
			}
			statementSearchAllTypes.setString(1, name);
			resultSetSearchName = statementSearchAllTypes.executeQuery();
			return resultSetSearchName;
		} catch (SQLException e) {
			DbUtils.safeCloseResultSet(resultSetSearchName);
			return null;
		}
	}

	/**
	 * Erstellt das ResultSet für eine Tabelle, die die Treffer der trunkierten
	 * Suche nach einem Schlagwort enthält.
	 * 
	 * @param name
	 *            nicht null, kann aber auch leer sein, dann wird der gesamte
	 *            Bestand zu einem Typ angezeigt.
	 * @param type
	 *            Datensatztyp (p,g,s, ...). Wird nicht auf Zulässigkeit
	 *            überprüft.
	 * 
	 * @return Modell oder null (bei sql-Fehler)
	 */
	public synchronized ResultSet getResultSetForNameAndType(String name,
			final char type) {
		ResultSet resultSetSearchNameAndType = null;
		// verschiedene utf-8-Formen von Umlauten etc. vereinheitlichen:
		name = Normalizer.normalize(name, Form.NFD);
		try {
			if (name.endsWith("*")) {
				name = name.substring(0, name.length() - 1) + '%';
			}

			statementSearchNameTypeTrunk.setString(2, name);
			statementSearchNameTypeTrunk.setString(1, String.valueOf(type));
			resultSetSearchNameAndType =
					statementSearchNameTypeTrunk.executeQuery();
			return resultSetSearchNameAndType;
		} catch (SQLException e) {
			DbUtils.safeCloseResultSet(resultSetSearchNameAndType);
			return null;
		}
	}

	/**
	 * Erstellt das eine Liste aus ids und Namen. Trunkierungszeichen ist *.
	 * 
	 * @param nameTrunc
	 *            nicht null, kann aber auch leer sein, dann wird der gesamte
	 *            Bestand zu einem Typ angezeigt.
	 * @param type
	 *            Datensatztyp (p,g,s, ...). Wird nicht auf Zulässigkeit
	 *            überprüft.
	 * 
	 * @return Collection eventuell leer (bei sql-Fehler)
	 */
	public synchronized Collection<Pair<String, String>>
			getIDsAndNamesForNameAndType(String nameTrunc, final char type) {

		Collection<Pair<String, String>> pairs = new LinkedList<>();
		ResultSet resultset = getResultSetForNameAndType(nameTrunc, type);
		if (resultset == null)
			return pairs;

		try {
			while (resultset.next()) {
				String id = resultset.getString("id");
				String name = resultset.getString("name");
				Pair<String, String> pair = new Pair<String, String>(id, name);
				pairs.add(pair);
			}
		} catch (SQLException e) {

		}
		DbUtils.safeCloseResultSet(resultset);
		return pairs;

	}

	/**
	 * Liefert ein ResultSet für die Suche nach einer idn.
	 * 
	 * @param id
	 *            id des Datensatzes
	 * @return Model oder null bei SQL-Fehler
	 */
	public synchronized ResultSet getResultsetForID(final String id) {
		ResultSet resultSetSearchID = null;
		try {
			statementSearchID.setString(1, id);
			resultSetSearchID = statementSearchID.executeQuery();
			return resultSetSearchID;
		} catch (SQLException e) {
			DbUtils.safeCloseResultSet(resultSetSearchID);
			return null;
		}
	}

	/**
	 * Holt zur idn den Namen aus der Tablle sw.
	 * 
	 * @param idn
	 *            nicht null
	 * @return Name oder null
	 */
	public synchronized String getNameForIDN(final String idn) {
		ResultSet resultSetSearchID = null;
		try {
			statementSearchID.setString(1, idn);
			resultSetSearchID = statementSearchID.executeQuery();
			resultSetSearchID.next();
			String name = resultSetSearchID.getString("name");
			DbUtils.safeCloseResultSet(resultSetSearchID);
			return name;
		} catch (SQLException e) {
			DbUtils.safeCloseResultSet(resultSetSearchID);
			return null;
		}
	}

	/**
	 * Gibt eine Liste von Paaren (id, name) zurück, die Unterbegriffe von obID
	 * sind.
	 * 
	 * @param obID
	 *            nicht null, nicht leer
	 * @return eventuell leere Liste
	 */
	public synchronized List<Pair<String, String>> getUBB(final String obID) {
		ResultSet resultSetSearchUBB = null;
		try {
			RangeCheckUtils.assertStringParamNotNullOrEmpty("id", obID);
			statementSearchUBB.setString(1, obID);
			resultSetSearchUBB = statementSearchUBB.executeQuery();
			List<Pair<String, String>> ubb = new LinkedList<>();
			while (resultSetSearchUBB.next()) {
				String id = resultSetSearchUBB.getString("id");
				String name = resultSetSearchUBB.getString("name");
				ubb.add(new Pair<String, String>(id, name));
			}
			DbUtils.safeCloseResultSet(resultSetSearchUBB);
			return ubb;
		} catch (SQLException e) {
			DbUtils.safeCloseResultSet(resultSetSearchUBB);
			System.err.println(e);
			return Collections.emptyList();
		}
	}

	/**
	 * Gibt eine Liste von Paaren (id, name) zurück, die Oberbegriffe von ubID
	 * sind.
	 * 
	 * @param ubID
	 *            nicht null, nicht leer
	 * @return eventuell leere Liste
	 */
	public synchronized List<Pair<String, String>> getOBB(final String ubID) {
		ResultSet resultSetSearchOBB = null;
		try {
			RangeCheckUtils.assertStringParamNotNullOrEmpty("id", ubID);
			statementSearchOBB.setString(1, ubID);
			resultSetSearchOBB = statementSearchOBB.executeQuery();
			List<Pair<String, String>> ubb = new LinkedList<>();
			while (resultSetSearchOBB.next()) {
				String id = resultSetSearchOBB.getString("id");
				String name = resultSetSearchOBB.getString("name");
				ubb.add(new Pair<String, String>(id, name));
			}
			DbUtils.safeCloseResultSet(resultSetSearchOBB);
			return ubb;
		} catch (SQLException e) {
			DbUtils.safeCloseResultSet(resultSetSearchOBB);
			System.err.println(e);
			return Collections.emptyList();
		}
	}

	public static void main(String[] args) throws SQLException {
		DBConnection connection = new DBConnection("./dbconfig/db.properties");
		System.out.println(connection.getNameForIDN("988798069"));
		System.out.println(connection.getIDsAndNamesForNameAndType(
				"Altblockflöte", 's'));
		connection.close();
	}

	/**
	 * Schließt alle Ressourcen.
	 */
	public synchronized void close() {

		DbUtils.safeCloseStatement(statementSearchAllTypes);
		DbUtils.safeCloseStatement(statementSearchID);
		DbUtils.safeCloseStatement(statementSearchNameTypeTrunk);
		DbUtils.safeCloseStatement(statementSearchUBB);

		DbUtils.safeCloseConnection(connection);

	}

	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		close();
	}

}
